Skip to content

feat(admin): generic per-entity Clone/Copy#109

Merged
windischb merged 3 commits into
developfrom
feat/entity-clone
Jun 30, 2026
Merged

feat(admin): generic per-entity Clone/Copy#109
windischb merged 3 commits into
developfrom
feat/entity-clone

Conversation

@windischb

Copy link
Copy Markdown
Contributor

What

Adds a right-click "Clone" affordance across the six admin entities —
App · OAuth Client · Scope · API · Role · Group. Slugs, client_ids and
audiences are immutable by design, so the way to "rename" an entity is to clone
it under a new identity and re-wire the references; this gives that one
consistent gesture.

How

One pattern for all (frontend-only — reuses the existing create endpoints, no
backend change):

  • New src/composables/useClone.ts: a module-level stash (stage/consume/clear,
    keyed per entity, single-use — the fragment-routed modals only carry an id
    slot, so the prefill rides out-of-band) + buildClonePrefill(source, descriptor)
    • six per-entity descriptors.
  • A List stages the prefill from the full source DTO (loadOne for
    App/Client/Scope/API; store .find for Role/Group whose list DTOs are
    complete) and opens the Create modal; the modal consumes on mount and maps
    via its existing fromDto (App/Client/Scope/API) or inline (Role/Group).

Per-entity shaping

  • App — blank Slug; catalog entries copied as new entries (fresh ids, so
    the source's role grants / RS subsets are untouched); drop the Origin
    override (subdomain is globally unique). Branding / registration / native-grant
    / DCR / CIMD overrides clone 1:1.
  • OAuth Client — blank ClientId; drop the hashed secret (a fresh one is
    minted on create) + DCR audit fields + Service-Account linkage.
  • Scope / API / Role / Group — blank the immutable identity (Name / aud);
    API drops Secrets; Group's last script error is not carried over.

Docs

A "Cloning" section on each of the six admin entity pages. Also corrects
App-settings references that #108 left stale (the removed settings/:id
modal + GET/PATCH /api/app/{id}/settings endpoint) in applications.md and the
admin-API reference → the inline model (GET /api/app/{id} + POST/PUT /api/app).

Verification

  • pnpm type-check + pnpm build green.
  • chrome-devtools visual smoke (0 console errors): App clone — GET-verified
    the copy has fresh catalog ids, Branding/SelfReg cloned, Origin absent, a
    single POST /api/app (no /settings call); Scope clone — Name blank,
    rest cloned; Group clone — members/roles/type/BoundTo cloned (this path is
    what surfaced the structuredClone-on-reactive-proxy bug, fixed in the second
    commit via a JSON deep-clone).

🤖 Generated with Claude Code

windischb and others added 3 commits June 30, 2026 18:26
Slugs, client_ids and audiences are immutable by design, so the way to
"rename" an entity is to clone it under a new identity and re-wire the
references. Add a right-click "Clone" affordance across the six admin
entities: App, OAuth Client, Scope, API, Role and Group.

One pattern for all: a `useClone()` composable holds a module-level stash
(the fragment-routed modals only carry an `id` slot, so the prefill rides
out-of-band) plus a tiny per-entity descriptor — identity fields blanked,
secrets/server-issued ids dropped, everything else copied 1:1. A List
stages the prefill from the full source DTO and opens the Create modal;
the modal consumes it on mount and maps it through its existing fromDto.

Per-entity shaping:
- App: blank Slug, null catalog-entry ids (they belong to the source's
  streams — the clone mints fresh ids), drop the Origin override (the
  subdomain is globally unique). Branding/registration/grant overrides
  clone 1:1 via the settings tab.
- OAuth Client: blank ClientId, drop the hashed secret (create mints a
  fresh one) + DCR audit fields + SA linkage.
- Scope / API / Role / Group: blank the immutable identity (Name / aud);
  drop API secrets; Group's last script error is not carried over.

Frontend-only — reuses the existing create endpoints, no backend change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Role/Group clone handlers pass the Pinia store entity (a Vue reactive
Proxy) straight into buildClonePrefill. structuredClone rejects a Proxy
with DataCloneError, so right-click → Klonen threw on those two lists
(App/Scope/API/Client went through a fresh loadOne object and happened to
work). Switch buildClonePrefill to a JSON round-trip: the DTOs are pure
JSON (no Dates/Sets/functions), so it is a faithful detached deep copy
and serialises straight through the reactive proxy.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a concise "Cloning" section to each admin entity page (Applications,
OAuth Clients/Scopes/APIs, Roles, Groups): how to invoke (list →
right-click → Clone), which immutable identity is blanked, and which
secrets/server-issued fields are dropped — framing clone as the way to
"rename" an entity whose identity can't be changed.

While in applications.md + the admin-API reference, correct the App
**settings** references that PR #108 made stale: the separate
`settings/:id` modal and `GET/PATCH /api/app/{id}/settings` endpoint are
gone — per-App ADR-0011 settings now ride inline on the App resource
(`GET /api/app/{id}` + `POST`/`PUT /api/app`, one tenant transaction,
replace semantics). A reader following the old docs would have hit a 404.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@windischb windischb merged commit fe39297 into develop Jun 30, 2026
8 checks passed
@windischb windischb deleted the feat/entity-clone branch June 30, 2026 18:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant